#ifndef __IO_NURBS__
#define __IO_NURBS__

#include <string>
#include <vector>
#include <fstream>
#include <iostream>
#include <iomanip>

#include "math/vector.h"

#include "node.h"
#include "mesh.h"

class Curve;
class Surface;
class STriangleMesh;
class NurbsSurfaceMesh;

Primitive* OpenPrimitive(const std::string& filename);
Curve* OpenCurve(const std::string& filename);
Surface* OpenSurface(const std::string& filename);
NurbsSurfaceMesh* OpenNurbsSurfaceMesh(const std::string& filename);
STriangleMesh* OpenTriangleMesh(const std::string& filename);

void WriteOFF(STriangleMesh* tm, const std::string& filename);

STriangleMesh ReadOBJ(const std::string& filename);

template<size_t dim, typename T>
void WriteOFF(const std::pair< std::vector< math::Vec<T, dim> >, std::vector< std::vector<size_t> > >& mesh, const std::string& filename) {
	std::ofstream file;
	file.open(filename.c_str());
	file << std::setprecision(8);
	
	file << "OFF" << std::endl;
	file << mesh.first.size() << " " << mesh.second.size() << " " << 0 << std::endl;
	
	for(size_t i = 0; i < mesh.first.size(); i++) {
		const math::Vec<T, dim>& P = mesh.first[i];
		for(size_t j = 0; j < dim; j++) {
			if(j == dim - 1)
				file << P[j] << std::endl;
			else
				file << P[j] << " ";
		}
	}
	
	for(size_t i = 0; i < mesh.second.size(); i++) {
		const size_t n = mesh.second[i].size();
		file << n;
		for(size_t j = 0; j < mesh.second[i].size(); j++)
			file << " " << mesh.second[i][j];
		file << std::endl;
	}
	
	file.close();
}

template<size_t dim, typename T>
void WriteOFF(const std::pair< std::vector< math::Vec<T, dim> >, std::vector< math::Vec<size_t, 3> > >& mesh, const std::string& filename) {
	std::vector< std::vector<size_t> > index;
	for(size_t i = 0; i < mesh.second.size(); i++) {
		std::vector<size_t> indexi(3);
		indexi[0] = mesh.second[i][0];
		indexi[1] = mesh.second[i][1];
		indexi[2] = mesh.second[i][2];
		index.push_back(indexi);
	}
	WriteOFF<dim, T>(std::pair< std::vector< math::Vec<T, dim> >, std::vector< std::vector<size_t> > >(mesh.first, index), filename);
}

template<size_t dimV, size_t dimE, typename T>
void WriteOFF(const std::pair< std::vector< math::Vec<T, dimV> >, std::vector< std::pair<std::vector<size_t>, math::Vec<T, dimE> > > >& mesh, const std::string& filename) {
	std::ofstream file;
	file.open(filename.c_str());
	file << std::setprecision(32);
	
	file << "OFF" << std::endl;
	file << mesh.first.size() << " " << mesh.second.size() << " " << 0 << std::endl;
	
	for(size_t i = 0; i < mesh.first.size(); i++) {
		const math::Vec<T, dimV>& P = mesh.first[i];
		for(size_t j = 0; j < dimV; j++) {
			if(j == dimV - 1)
				file << P[j] << std::endl;
			else
				file << P[j] << " ";
		}
	}
	
	for(size_t i = 0; i < mesh.second.size(); i++) {
		const std::vector<size_t>& index = mesh.second[i].first;
		const size_t n = index.size();
		file << n;
		for(size_t j = 0; j < index.size(); j++)
			file << " " << index[j];

		const math::Vec<T, dimE>& P = mesh.second[i].second;
		for(size_t j = 0; j < dimE; j++)
			file << " " << P[j];

		file << std::endl;
	}
	
	file.close();
}

template<size_t dim, typename T1, typename T2>
std::pair< std::vector< math::Vec<T1, dim> >, std::vector< std::vector<T2> > > ReadOFF(const std::string& filename) {
	std::ifstream file;
	file.open(filename.c_str());
	if(!file) {
		std::cerr << "Could not open file \"" << filename << "\"." << std::endl;
		return std::pair< std::vector<math::Vec<T1, dim> >, std::vector< std::vector<T2> > >();
	}

	std::string type;
	file >> type;

	int numVertices, numElements, temp;
	file >> numVertices;
	file >> numElements;
	file >> temp;

	std::vector< math::Vec<T1, dim> > vertex(numVertices);
	file >> std::setprecision(32);
	for(int i = 0; i < numVertices; i++) {
		math::Vec<T1, dim>& v = vertex[i];
		for(size_t j = 0; j < dim; j++)
			file >> v[j];
	}

	std::vector< std::vector<T2> > index(numElements);
	for(int i = 0; i < numElements; i++) {
		file >> temp;
		index[i] = std::vector<T2>(temp);
		for(size_t j = 0; j < temp; j++)
			file >> index[i][j];
	}

	return std::pair< std::vector< math::Vec<T1, dim> >, std::vector< std::vector<T2> > >(vertex, index);
}

///////////////////////////////////////////////////////////

template<class type>
void PrintMatrix(const boost::numeric::ublas::mapped_matrix<type>& M, size_t precision = 4) {
	std::cout << std::fixed;
	for(size_t i = 0; i < M.size1(); i++) {
		for(size_t j = 0; j < M.size2(); j++) {
			std::cout << M(i, j) << " " ;
		}
		std::cout << std::endl;
	}
}

template<class type>
void PrintMatrix(const boost::numeric::ublas::matrix<type>& M, size_t precision = 4) {
	std::cout << std::fixed;
	for(unsigned i = 0; i < M.size1(); i++) {
		for(unsigned j = 0; j < M.size2(); j++) {
			if(M(i, j) >= 0.0)
				std::cout << " " << M(i, j) << " ";
			else
				std::cout << M(i, j) << " ";
		}
		std::cout << std::endl;
	}
}

template<size_t D, typename T>
void SavePoints(const std::vector< math::Vec<T, D> >& point, const std::string& filename, size_t precision = 32) {
	std::ofstream file;
	file.open(filename.c_str());
	file << std::setprecision(precision);
	for(size_t i = 0; i < point.size(); i++) {
		const math::Vec<T, D>& P = point[i];
		for(size_t j = 0; j < D; j++)
			file << P[j] << " ";
		file << std::endl;
	}

	file.close();
}

template<typename T, size_t D>
std::vector< math::Vec<T, D> > OpenPoints1(const std::string& filename) {
	std::ifstream file;
	file.open(filename.c_str());
	if(!file) {
		std::cerr << "Could not open file \"" << filename << "\"." << std::endl;
		return std::vector<math::Vec<T, D> >();
	}
	
	std::vector< math::Vec<T, D> > point;
	while(file) {
		math::Vec<T, D> P;
		for(size_t j = 0; j < D; j++)
			file >> P[j];
		point.push_back(P);
	}
	point.pop_back();

	file.close();

	return point;
}

#endif //__IO_NURBS__
